/**
 * Copyright 2012 Simple-Jdbc-Lightweight

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package org.mlu.jdbc.simple.dao;

import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.mlu.jdbc.simple.SimpleJdbc;
import org.mlu.jdbc.simple.utils.JdbcUtils;
import org.mlu.jdbc.simple.utils.ReflectionUtil;
import org.mlu.jdbc.simple.utils.SchemaUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * Implements basic CRUD operations. Extends SimpleJdbc 
 * @see SimpleJdbc
 * 
 * @author Mlungisi Sincuba
 * @since 1.0
 */
public class GenericDaoImpl<T> extends SimpleJdbc implements GenericDao<T> {

    protected static final Logger logger = LoggerFactory.getLogger(GenericDaoImpl.class);
    private final static String SQL_INSERT = "INSERT INTO ";
    private final static String SQL_UPDATE = "UPDATE ";
    private final static String SQL_DELETE = "DELETE FROM ";
    private final static String SQL_SELECT = "SELECT * FROM ";
    
    private Class<T> surrogateClass;
    private String tableName;

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * 
     * @see SimpleJdbc#SimpleJdbc() 
     */
    public GenericDaoImpl(Class<T> surrogateClass) {
        this.surrogateClass = surrogateClass;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param dataSource data source to be used
     * 
     * @see SimpleJdbc#SimpleJdbc(javax.sql.DataSource) 
     */
    public GenericDaoImpl(Class<T> surrogateClass, DataSource dataSource) {
        super(dataSource);
        this.surrogateClass = surrogateClass;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param fileName properties file name for data source details
     * 
     * @see SimpleJdbc#SimpleJdbc(java.lang.String) 
     * 
     */
    public GenericDaoImpl(Class<T> surrogateClass, String fileName) {
        super(fileName);
        this.surrogateClass = surrogateClass;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param input 
     */
    public GenericDaoImpl(Class<T> surrogateClass, InputStream input) {
        super(input);
        this.surrogateClass = surrogateClass;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param tableName db table name, class name is used by convention if left as null
     * @param dataSource 
     */
    public GenericDaoImpl(Class<T> surrogateClass, String tableName, DataSource dataSource) {
        this(surrogateClass, dataSource);
        this.tableName = tableName;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param tableName db table name, class name is used by convention if left as null
     * @param input input stream for web applications
     * 
     * @see SimpleJdbc#SimpleJdbc(java.io.InputStream) 
     */
    public GenericDaoImpl(Class<T> surrogateClass, String tableName, InputStream input) {
        this(surrogateClass, input);
        this.tableName = tableName;
    }

    /**
     * 
     * @param surrogateClass class which represents db table structure
     * @param tableName db table name, class name is used by convention if left as null
     * @param fileName database properties file name
     * 
     * @see SimpleJdbc#SimpleJdbc(java.lang.String) 
     * 
     */
    public GenericDaoImpl(Class<T> surrogateClass, String tableName, String fileName) {
        this(surrogateClass, fileName);
        this.tableName = tableName;
    }
    
    /**
     * @see GenericDao#addNew(java.lang.Object) 
     */
    public int addNew(T t) {
        getpopulateTableName(t);
        int rows = 0;
        try {
            
            StringBuilder sb = new StringBuilder(SQL_INSERT).append(tableName).append("(");
            Method[] declaredMethods = t.getClass().getDeclaredMethods();
            List<Object> paramList = new ArrayList<Object>();
            StringBuilder sbPlaceHolders = new StringBuilder(" VALUES(");
            for (Method method : declaredMethods) {
                String name = method.getName();
                if (name.startsWith("get") || name.startsWith("is")) {
                    populateParamsArray(t, name, paramList);
                    String fieldName = getFieldNameFromMethodName(name);
                    sb.append(fieldName).append(", ");
                    sbPlaceHolders.append("?, ");
                } 
            }
            Object[] params = paramList.toArray();
            
            sbPlaceHolders.replace(sbPlaceHolders.lastIndexOf(", "), sbPlaceHolders.length(), ")");
            sb.replace(sb.lastIndexOf(", "), sb.length(), ")");
            sb.append(sbPlaceHolders);
            rows = update(sb.toString(), params);

        } catch (NoSuchMethodException ex) {
            logger.error("Method not found", ex);
        } catch (IllegalAccessException ex) {
            logger.error("Access not allowed", ex);
        } catch (IllegalArgumentException ex) {
            logger.error("Argument mismatch", ex);
        } catch (InvocationTargetException ex) {
            logger.error("Not valid invocation", ex);
        } catch (SecurityException ex) {
            logger.error("Security error", ex);
        }
        return rows;
    }

    /**
     * @see GenericDao#update(java.lang.Object, java.io.Serializable) 
     */
    public int update(T t, Serializable id) {
        getpopulateTableName(t);
        int rows = 0;
        try {
            
            StringBuilder sb = new StringBuilder(SQL_UPDATE).append(tableName).append(" SET ");
            Method[] declaredMethods = t.getClass().getDeclaredMethods();
            List<Object> paramList = new ArrayList<Object>();
            String pk = SchemaUtil.getPrimaryKey(tableName, getDataSource());
            for (Method method : declaredMethods) {
                String name = method.getName();
                if (name.startsWith("get") || name.startsWith("is")) {
                    populateParamsArray(t, name, paramList);
                    String columnName = getFieldNameFromMethodName(name);
                    sb.append(columnName).append(" = ?,");
                } 
            }
            paramList.add(id);
            Object[] params = paramList.toArray();
            
            sb.replace(sb.lastIndexOf(","), sb.length(), " WHERE ");
            
            sb.append(pk).append(" = ?");
            logger.debug("update sql sb = {}", sb);
            logger.debug("parameters: {}", paramList);
            rows = update(sb.toString(), params);

        } catch (SQLException ex) {
            logger.error("SQL Error", ex);
        } catch (NoSuchMethodException ex) {
            logger.error("Method not found", ex);
        } catch (IllegalAccessException ex) {
            logger.error("Access not allowed", ex);
        } catch (IllegalArgumentException ex) {
            logger.error("Argument mismatch", ex);
        } catch (InvocationTargetException ex) {
            logger.error("Not valid invocation", ex);
        } catch (SecurityException ex) {
            logger.error("Security error", ex);
        }
        return rows;
    }

    /**
     * @see GenericDao#remove(java.io.Serializable) 
     */
    public int remove(Serializable id) {
        int rows = 0;
        try {
            
            StringBuilder sb = new StringBuilder(SQL_DELETE).append(tableName).append(" WHERE ");
            List<Object> paramList = new ArrayList<Object>();
            paramList.add(id);
            Object[] params = paramList.toArray();
           
            String pk = SchemaUtil.getPrimaryKey(tableName, getDataSource());
            sb.append(pk).append(" = ?");
            logger.debug("update sql sb = {}", sb);
            logger.debug("parameters: {}", paramList);
            rows = update(sb.toString(), params);

        } catch (SQLException ex) {
            logger.error("SQL Error", ex);
        } 
        return rows;
    }

    /**
     * @see GenericDao#findById(java.io.Serializable) 
     */
    public T findById(Serializable id) {
       T t = null;
   
        try {
            
            StringBuilder sb = new StringBuilder(SQL_SELECT).append(tableName).append(" WHERE ");
            List<Object> paramList = new ArrayList<Object>();
            paramList.add(id);
            Object[] params = paramList.toArray();
           
            String pk = SchemaUtil.getPrimaryKey(tableName, getDataSource());
            sb.append(pk).append(" = ?");
            logger.debug("update sql sb = {}", sb);
            logger.debug("parameters: {}", paramList);
            t = (T) this.queryForBean(sb.toString(), surrogateClass, params);

        } catch (SQLException ex) {
            logger.error("SQL Error", ex);
        } 
        return t;
    }

    /**
     * @see GenericDao#findAll() 
     */
    public List<T> findAll() {
        List<T> tList = null;
        StringBuilder sb = new StringBuilder(SQL_SELECT).append(tableName);
        tList = queryForBeanList(sb.toString(), surrogateClass);

        return tList;
    }
    
    private String getFieldNameFromMethodName(String methodName) {
        if (methodName.startsWith("is")) {
            methodName = methodName.substring(2);
        } else if (methodName.startsWith("get")){
            methodName = methodName.substring(3);
        }
        methodName = methodName.substring(0, 1).toLowerCase() + methodName.substring(1);
        methodName = JdbcUtils.convertPropertyNameToUnderscoreName(methodName);
        return methodName;
    }

    private void populateParamsArray(T t, String name, List<Object> paramList) throws IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
        Object returned = ReflectionUtil.invokeMethod(t, name);
        paramList.add(returned.toString().trim());
    }

    private void getpopulateTableName(T t) {
        String className = t.getClass().getSimpleName();
        if (tableName == null) {
            tableName = JdbcUtils.convertClassNameToTableName(className);
        }
        logger.debug("tableName is {}", tableName);
        logger.debug("className is {}", className);
    }
    
}
